/**
 * 带元数据的增强错误类
 */
export class MayError extends Error {
  /**
   * 错误名称
   */
  public readonly name: string = 'MayError';

  /**
   * 错误元数据
   */
  public meta?: Record<string, unknown>;

  /**
   * 创建 MayError 实例
   *
   * @param message - 错误消息
   * @param options - 错误选项（继承自 Error 类）
   * @param meta - 错误元数据，用于存储额外的上下文信息
   */
  constructor(message: string, options?: ErrorOptions, meta?: Record<string, unknown>) {
    super(message, options);
    this.meta = meta;
  }
}

/**
 * 可能出错的函数执行器
 *
 * 用于包装可能抛出异常的同步或异步函数，统一处理错误并添加元数据
 * 对于异步函数，会在 Promise rejection 时捕获错误
 *
 * @template T - 被包装函数的类型
 * @param fn - 要执行的函数
 * @param options - 配置选项
 * @param options.meta - 错误元数据或获取元数据的函数
 * @param options.kind - 错误类型，默认为 MayError
 * @returns 函数执行的结果
 */
export function may<T extends () => unknown>(
  fn: T,
  {
    meta,
    kind = MayError,
  }: {
    meta?: Record<string, unknown> | (() => Record<string, unknown>);
    kind?: typeof MayError;
  } = {}
): ReturnType<T> {
  try {
    // 执行目标函数
    const result = fn();

    // 检查是否为 Promise
    // @ts-ignore
    if (typeof result === 'object' && result !== null && typeof result.then === 'function') {
      // @ts-ignore
      return (async () => {
        try {
          return await result;
        } catch (err) {
          throwMayError(err, { meta: typeof meta === 'function' ? meta() : meta, kind });
        }
      })();
    }

    return result as ReturnType<T>;
  } catch (err) {
    throwMayError(err, { meta: typeof meta === 'function' ? meta() : meta, kind });
  }
}

/**
 * 错误处理函数，用于统一抛出 MayError 类型的错误
 *
 * 该函数会根据传入的错误类型进行不同的处理：
 * - 如果已经是 MayError 实例，则合并元数据后重新抛出
 * - 如果是普通 Error 实例，则包装为 MayError 后抛出
 * - 如果是其他类型，则转换为字符串后包装为 MayError 抛出
 *
 * @param err - 需要处理的错误对象
 * @param options - 包含错误处理选项的对象
 * @param options.meta - 附加的元数据信息
 * @param options.kind - 错误类型，默认为 MayError
 * @returns 永远不会返回，总是抛出错误
 */
export function throwMayError(
  err: unknown,
  {
    meta,
    kind = MayError,
  }: {
    meta?: Record<string, unknown>;
    kind?: typeof MayError;
  } = {}
): never {
  if (err instanceof kind) {
    // 没有设置元数据：直接抛出
    if (meta === undefined) {
      throw err;
    }

    if (err.meta) {
      // 已有元数据：合并新元数据
      for (const [k, v] of Object.entries(meta)) {
        err.meta[k] = v;
      }
    } else {
      // 没有元数据：直接设置
      err.meta = meta;
    }

    throw err;
  } else if (err instanceof MayError) {
    throw new kind(err.message, { cause: err.cause }, { ...err.meta, ...meta });
  } else if (Error.isError(err)) {
    throw new kind(err.message, { cause: err }, meta);
  }
  throw new kind(String(err), { cause: err }, meta);
}
